/*
*	Copyright(c) 2020 lutianming email：641471957@qq.com
*
*	Sheeps may be copied only under the terms of the GNU Affero General Public License v3.0
*/


#include "framework.h"
#include "AgentProtocol.h"
#include "AgentProtocolSub.h"
#include "AgentManager.h"
#ifdef __WINDOWS__
#include "io.h"
#include "direct.h"
#else
#include "dirent.h"
#endif // __WINDOWS__


int clogId = -1;
time_t authtime = 0;

static std::map<std::string, t_file_info> updatefile, allfile, localfile;

int ClientLogInit(const char *configfile)
{
	task_agent_stat_msg_set(STATE_READY_NONE, "未准备");
	const char* file = config_get_string_value("agent", "log_file", NULL);
	if (file) {
		char fullpath[256] = { 0x0 };
		snprintf(fullpath, sizeof(fullpath), "%s/%s", LogPath, file);
		int loglevel = config_get_int_value("agent", "log_level", LOG_NORMAL);
		int maxsize = config_get_int_value("agent", "log_size", 50);
		int timesplit = config_get_int_value("agent", "log_time", 0);
		int maxfile = config_get_int_value("agent", "log_maxfile", 0);
		clogId = RegisterLog(fullpath, loglevel, maxsize, timesplit, maxfile);
	}
	return 0;
}

static int MsgResponse(HSOCKET hsock, int cmdNo, int retCode, const char* msg)
{
	char buf[128] = { 0x0 };
	int len = snprintf(buf, sizeof(buf), "{\"retCode\":%d,\"retMsg\":\"%s\"}", retCode, msg);
	t_stress_protocol_head head = {0x0};
	head.cmdNo = cmdNo;
	head.msgLen = len + SHEEPS_HEAD_SIZE;
	
	char sockbuf[256] = { 0x0 };
	memcpy(sockbuf, &head, SHEEPS_HEAD_SIZE);
	memcpy(sockbuf + SHEEPS_HEAD_SIZE, buf, len);
	HsocketSend(hsock, sockbuf, head.msgLen);
	return 0;
}

static int MsgSend(HSOCKET hsock, int cmdNo, char* data, int len)
{
	t_stress_protocol_head head = {0x0};
	head.cmdNo = cmdNo;
	head.msgLen = len + SHEEPS_HEAD_SIZE;

	char sockbuf[256] = { 0x0 };
	memcpy(sockbuf, (char*)&head, SHEEPS_HEAD_SIZE);
	memcpy(sockbuf + SHEEPS_HEAD_SIZE, data, len);
	HsocketSend(hsock, sockbuf, head.msgLen);
	return 0;
}

int client_cmd_report_client_info(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* ret = cJSON_GetObjectItem(root, "ret");
	if (ret->valueint != 0) {
		cJSON* msg = cJSON_GetObjectItem(root, "msg");
		task_agent_stat_msg_set(STATE_READY_ERROR, msg->valuestring);
		HsocketClose(hsock);
		return -1;
	}
	cJSON* authcode = cJSON_GetObjectItem(root, "AuthCode");
	if (authcode == NULL || authcode->type != cJSON_String)
		return -1;
	return 0;
}

int client_cmd_task_init(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* taskId = cJSON_GetObjectItem(root, "TaskID");
	cJSON* runNumber = cJSON_GetObjectItem(root, "runNumber");
	cJSON* userExecuteCycle = cJSON_GetObjectItem(root, "UserExecuteCycle");
	cJSON* projectId = cJSON_GetObjectItem(root, "projectID");
	cJSON* userCount = cJSON_GetObjectItem(root, "UserCount");
	cJSON* userIndex = cJSON_GetObjectItem(root, "UserIndex");
	cJSON* machineId = cJSON_GetObjectItem(root, "MachineID");
	cJSON* ignoreErr = cJSON_GetObjectItem(root, "IgnoreErr");
	cJSON* autoStop = cJSON_GetObjectItem(root, "AtuoStop");
	cJSON* logLevel = cJSON_GetObjectItem(root, "LogLevel");
	cJSON* logReport = cJSON_GetObjectItem(root, "LogReport");
	cJSON* parms = cJSON_GetObjectItem(root, "Parms");
	if (!taskId || !runNumber || !userExecuteCycle || !projectId || !userCount || !userIndex || !machineId || !logLevel || !logReport || !parms ||
		taskId->type != cJSON_Number || runNumber->type != cJSON_Number || !cJSON_IsNumber(userExecuteCycle) || projectId->type != cJSON_Number ||
		userCount->type != cJSON_Number || !cJSON_IsNumber(userIndex) || machineId->type != cJSON_Number ||
		logLevel->type != cJSON_Number || !cJSON_IsNumber(logReport) || parms->type != cJSON_String)
	{
		MsgResponse(hsock, cmdNO, 1, "参数错误");
		return -1;
	}

	int taskid = taskId->valueint;
	int runnumber = runNumber->valueint;
	int userexecutecycle = userExecuteCycle->valueint;
	int projectid = projectId->valueint;
	int machineid = machineId->valueint;
	bool ignorerr = false;
	if (ignoreErr != NULL && ignoreErr->type == cJSON_True)
		ignorerr = true;
	bool autostop = true;
	if (autoStop != NULL && autoStop->type == cJSON_False)
		autostop = false;
	int usercount = userCount->valueint;
	int userindex = userIndex->valueint;
	int loglevel = logLevel->valueint;
	int logreport = logReport->valueint;
	char* sparms = parms->valuestring;

	create_new_task(taskid, runnumber, userexecutecycle, projectid, machineid, ignorerr, autostop, usercount, userindex, loglevel, logreport, sparms);
	return 0;
}

int client_cmd_task_case_add(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* taskId = cJSON_GetObjectItem(root, "TaskID");
	cJSON* event = cJSON_GetObjectItem(root, "EventType");
	cJSON* protocol = cJSON_GetObjectItem(root, "ConnType");
	cJSON* ip = cJSON_GetObjectItem(root, "Host");
	cJSON* port = cJSON_GetObjectItem(root, "Port");
	cJSON* sessionid = cJSON_GetObjectItem(root, "ShortKey");
	cJSON* timestamp = cJSON_GetObjectItem(root, "Timestamp");
	cJSON* microsecond = cJSON_GetObjectItem(root, "Microsecond");
	cJSON* msg = cJSON_GetObjectItem(root, "Msg");
	cJSON* note = cJSON_GetObjectItem(root, "Note");
	if (taskId == NULL  || event == NULL|| !protocol || ip == NULL || port == NULL || !sessionid ||timestamp == NULL || microsecond == NULL ||
		taskId->type != cJSON_Number || !cJSON_IsNumber(event) || !cJSON_IsNumber(protocol) || ip->type != cJSON_String || port->type != cJSON_Number ||
		!cJSON_IsNumber(sessionid) || timestamp->type != cJSON_Number || microsecond->type != cJSON_Number)
	{
		MsgResponse(hsock, cmdNO, 1, "参数错误");
		return -1;
	}

	int realTaskID = taskId->valueint;
	int EventType = event->valueint;
	insert_message_by_taskId(realTaskID, EventType, protocol->valueint, ip->valuestring, port->valueint, sessionid->valueint, msg->valuestring, note->valuestring, timestamp->valueint, microsecond->valueint);

	return 0;
}

int client_cmd_task_finish(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* taskId = cJSON_GetObjectItem(root, "TaskID");
	if (taskId == NULL || taskId->type != cJSON_Number)
	{
		MsgResponse(hsock, cmdNO, 1, "参数错误");
		return -1;
	}
	int realTaskID = taskId->valueint;
	stop_task_by_id(realTaskID);
	LOG(clogId, LOG_DEBUG, "task finish taskid[%d]\r\n", realTaskID);
	return 0;
}

int client_cmd_task_reinit(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* taskId = cJSON_GetObjectItem(root, "TaskID");
	cJSON* loop = cJSON_GetObjectItem(root, "Loop");
	if (taskId == NULL || loop == NULL || taskId->type != cJSON_Number || (loop->type != cJSON_True && loop->type != cJSON_False))
	{
		//MsgResponse(hsock, cmdNO, 1, "参数错误");
		return -1;
	}
	int realTaskID = taskId->valueint;
	insert_message_by_taskId(realTaskID, TYPE_REINIT, 0, NULL, loop->valueint, 0, NULL, NULL, 0, 0);
	LOG(clogId, LOG_DEBUG, "task reinit task[%d] flag[%d]\r\n", realTaskID, loop->valueint);
	return 0;
}

#ifdef __WINDOWS__
static void getFiles(char* path, std::map<std::string, t_file_info>* files)
{
	intptr_t hFile = 0;
	struct _finddata_t fileinfo;
	
	char allfile[256] = { 0x0 };
	snprintf(allfile, sizeof(allfile), "%s/*", path);

	char fullpath[256] = { 0x0 };

	if ((hFile = _findfirst(allfile, &fileinfo)) != -1){
		do{
			memset(fullpath, 0, sizeof(fullpath));
			if ((fileinfo.attrib & _A_SUBDIR)){
				snprintf(fullpath, sizeof(fullpath), "%s/%s", path, fileinfo.name);
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(fullpath, files);
			}
			else{
				snprintf(fullpath, sizeof(fullpath), "%s/%s", path, fileinfo.name);
				t_file_info info = { 0x0 };
				getfilemd5view(fullpath, info.fmd5, sizeof(info.fmd5));

				files->insert(std::pair<std::string, t_file_info>(fullpath, info));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}
#else
static void getFiles(char* path, std::map<std::string, t_file_info>* files){
	DIR* dir;
	struct dirent* ptr;
	char fullpath[256] = { 0x0 };
	if ((dir = opendir(path)) == NULL)
		return;
	while ((ptr = readdir(dir)) != NULL){
		if (ptr->d_type == DT_DIR){
			if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
				continue;
			snprintf(fullpath, sizeof(fullpath), "%s/%s", path, ptr->d_name);
			getFiles(fullpath, files);
		}
		else{
			snprintf(fullpath, sizeof(fullpath), "%s/%s", path, ptr->d_name);
			t_file_info info = { 0x0 };
			getfilemd5view(fullpath, info.fmd5, sizeof(info.fmd5));

			files->insert(std::pair<std::string, t_file_info>(fullpath, info));
		}
		
	}
	closedir(dir);
}
#endif

int sendUpfileMsg(HSOCKET hsock)
{
	if (updatefile.empty())
	{
		LOG(clogId, LOG_DEBUG, "%s-%d files sync done!\r\n", __func__, __LINE__);
		int ret = task_filesync_done();
		if (ret != 0)
		{
			LOG(clogId, LOG_ERROR, "%s-%d files sync function error!\r\n", __func__, __LINE__);
			MsgResponse(hsock, C2S_Agent_File_Sync_Done, -1, "Fail");
			//task_agent_stat_msg_set(STATE_READY_ERROR, "准备失败，请检查程序运行是否正常");
			return 0;
		}
		task_agent_stat_msg_set(STATE_READY_DONE, "准备");
		MsgResponse(hsock, C2S_Agent_File_Sync_Done, 0, "OK");
		return 1;
	}
	std::map<std::string, t_file_info>::iterator itt;
	itt = updatefile.begin();
	char buff[512] = { 0x0 };
	size_t size = 5120;
	if (itt->second.size - itt->second.offset < size)
		size = itt->second.size - itt->second.offset;
	int n = snprintf(buff, sizeof(buff), "{\"File\":\"%s\",\"Offset\":%zd,\"Size\":%zd}", itt->first.c_str(), itt->second.offset, size);
	MsgSend(hsock, C2S_Agent_Download_File, buff, n);
	return 0;
}

int client_cmd_sync_files(HSOCKET hsock, int cmdNO, cJSON* root)
{
	task_agent_stat_msg_set(STATE_READY_NONE, "未准备");
	cJSON* files = cJSON_GetObjectItem(root, "filelist");
	if (files == NULL || files->type != cJSON_Array){
		//MsgResponse(hsock, cmdNO, 1, "参数错误");
		return -1;
	}
	if (AgentSyncFile == 0){
		LOG(clogId, LOG_FAULT, "dev mode, give up file sync!\r\n");
		sendUpfileMsg(hsock);
		return 0;
	}

	updatefile.clear();
	allfile.clear();
	localfile.clear();
	getFiles(ProjectPath, &localfile);

	std::map<std::string, t_file_info>::iterator it, iter;

	char fullpath[256] = { 0x0 };
	int size = cJSON_GetArraySize(files);
	for (int i = 0; i < size; i++)
	{
		cJSON* item = cJSON_GetArrayItem(files, i);
		cJSON* file = cJSON_GetObjectItem(item, "File");
		cJSON* size = cJSON_GetObjectItem(item, "Size");
		cJSON* fmd5 = cJSON_GetObjectItem(item, "Md5");
		if (file == NULL || size == NULL || fmd5 == NULL ||
			file->type != cJSON_String || size->type != cJSON_Number || fmd5->type != cJSON_String)
		{
			LOG(clogId, LOG_DEBUG, "%s:%d pram error", __func__, __LINE__);
			continue;
		}
		
		t_file_info finfo = { 0x0 };
		memset(&finfo, 0, sizeof(t_file_info));
		snprintf(finfo.fmd5, sizeof(finfo.fmd5), fmd5->valuestring);
		finfo.size = size->valueint;
		
		memset(fullpath, 0, sizeof(fullpath));
		snprintf(fullpath, sizeof(fullpath), "%s%s", EXE_Path, file->valuestring);

		allfile.insert(std::pair<std::string, t_file_info>(fullpath, finfo));

		it = localfile.find(fullpath);
		if (it != localfile.end()){
			if (strcmp(it->second.fmd5, fmd5->valuestring) == 0){
				continue;
			}
		}
		updatefile.insert(std::pair<std::string, t_file_info>(std::string(file->valuestring), finfo));
	}
	
	for (it = localfile.begin(); it != localfile.end(); ++it){
		iter = allfile.find(it->first.c_str());
		if (iter == allfile.end())
#ifdef __WINDOWS__
			DeleteFileA(it->first.c_str());
#else
			remove(it->first.c_str());
#endif
	}
	//MsgResponse(hsock, cmdNO, 0, "OK");
	LOG(clogId, LOG_DEBUG, "%s-%d files sync start!\r\n", __func__, __LINE__);
	sendUpfileMsg(hsock);
	return 0;
}

int client_cmd_download_file(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* file = cJSON_GetObjectItem(root, "File");
	cJSON* offset = cJSON_GetObjectItem(root, "Offset");
	cJSON* order = cJSON_GetObjectItem(root, "Order");
	cJSON* size = cJSON_GetObjectItem(root, "Size");
	cJSON* data = cJSON_GetObjectItem(root, "Data");
	if (file == NULL || offset == NULL || order == NULL || size == NULL || data == NULL
		|| file->type != cJSON_String ||offset->type != cJSON_Number || order->type != cJSON_Number
		|| size->type != cJSON_Number ||data->type != cJSON_String){
		return -1;
	}
	std::map<std::string, t_file_info>::iterator itt;
	itt = updatefile.find(file->valuestring);
	if (itt == updatefile.end()){
		LOG(clogId, LOG_ERROR, "%s:%d file not found\r\n", __func__, __LINE__);
		return -1;
	}

	if (itt->second.file == NULL){
		char fullpath[256] = { 0x0 };
		snprintf(fullpath, sizeof(fullpath), "%s%s", EXE_Path, file->valuestring);
		CreateFileDirectory(fullpath);
#ifdef __WINDOWS__
		fopen_s(&itt->second.file, fullpath, "wb");
#else
		itt->second.file = fopen(fullpath, "wb");
#endif
		if (itt->second.file == NULL){
			LOG(clogId, LOG_ERROR, "%s:%d open file error\r\n", __func__, __LINE__);
			return -1;
		}
	}
	char buf[10240] = { 0x0 };
	size_t n = base64_decode(data->valuestring, strlen(data->valuestring), buf);
	fwrite(buf, n, 1, itt->second.file);

	itt->second.offset += size->valueint;
	if (itt->second.offset == itt->second.size){
		fclose(itt->second.file);
		updatefile.erase(itt);
	}

	sendUpfileMsg(hsock);
	return 0;
}

int client_cmd_task_change_user_count(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* taskId = cJSON_GetObjectItem(root, "TaskID");
	cJSON* change = cJSON_GetObjectItem(root, "Change");
	cJSON* userIndex = cJSON_GetObjectItem(root, "UserIndex");
	if (taskId == NULL || change == NULL || userIndex == NULL || taskId->type != cJSON_Number || change->type != cJSON_Number|| !cJSON_IsNumber(userIndex) ){
		return -1;
	}

	task_add_user_by_taskid(taskId->valueint, change->valueint, userIndex->valueint);
	return 0;
}

int client_cmd_charge_task_loglevel(HSOCKET hsock, int cmdNO, cJSON* root)
{
	cJSON* loglevel = cJSON_GetObjectItem(root, "LogLevel");
	cJSON* taskid = cJSON_GetObjectItem(root, "TaskID");
	if (loglevel == NULL || taskid == NULL || loglevel->type != cJSON_Number || taskid->type != cJSON_Number)
	{
		return -1;
	}
	set_task_log_level(loglevel->valueint, taskid->valueint);
	return 0;
}

int client_cmd_task_report_log(HSOCKET hsock, int cmdNO, cJSON* root) {
	cJSON* taskid = cJSON_GetObjectItem(root, "task_id");
	std::map<int, TaskReportLogInfo*>::iterator iter = ReportLogInfos.find(taskid->valueint);
	AgentProto->TaskTeportLogFile(iter->second);
	return 0;
}

std::map<int, client_cmd_cb> ClientFunc{
	{C2S_Agent_Report_Info,		client_cmd_report_client_info},
	{S2C_Task_Init,				client_cmd_task_init},
	{S2C_Task_Add_Case,			client_cmd_task_case_add},
	{S2C_Task_Finish,			client_cmd_task_finish},
	{S2C_Task_Stop_Case,		client_cmd_task_reinit},
	{S2C_Agent_File_Sync,		client_cmd_sync_files},
	{C2S_Agent_Download_File,	client_cmd_download_file},
	{S2C_Task_Add_User,			client_cmd_task_change_user_count},
	{S2C_Task_Log_Level,		client_cmd_charge_task_loglevel},
	{C2S_Task_Report_Log,		client_cmd_task_report_log}
};

void do_client_func_by_cmd(HSOCKET hsock, int cmdNO, cJSON* root)
{
	std::map<int, client_cmd_cb>::iterator iter = ClientFunc.find(cmdNO);
	if (iter != ClientFunc.end())
		iter->second(hsock, cmdNO, root);
}